<template>
  <div class="max-w-4xl mx-auto p-6">
    <h1 class="text-2xl font-semibold">图片裁剪（Crop）</h1>
    <p class="mt-2 text-gray-600">上传图片后，在预览图上拖拽选择裁剪区域。支持固定比例、旋转和导出裁剪结果。</p>

    <div class="mt-6 grid grid-cols-1 md:grid-cols-3 gap-6">
      <!-- 左侧：上传与操作 -->
      <div class="space-y-4">
        <label class="block">
          <span class="sr-only">选择图片文件</span>
          <input type="file" accept="image/*" @change="onFileChange" />
        </label>

        <div class="space-y-2">
          <label class="text-sm text-gray-700">裁剪比例</label>
          <select v-model="aspect" class="block w-full border rounded p-2">
            <option :value="null">自由</option>
            <option value="1">1:1</option>
            <option value="4/3">4:3</option>
            <option value="16/9">16:9</option>
          </select>
        </div>

        <div class="flex items-center space-x-2">
          <button class="bg-blue-600 text-white py-2 px-3 rounded" @click="rotate(-90)">左转 90°</button>
          <button class="bg-blue-600 text-white py-2 px-3 rounded" @click="rotate(90)">右转 90°</button>
        </div>

        <div class="space-y-2">
          <label class="text-sm text-gray-700">导出文件名</label>
          <input v-model="outputName" class="block w-full border rounded p-2" />
        </div>

        <div class="flex space-x-2">
          <button class="bg-blue-600 text-white py-2 px-3 rounded disabled:opacity-50" :disabled="!imgSrc" @click="doCrop">裁剪并预览</button>
          <button class="bg-gray-100 text-gray-800 py-2 px-3 rounded border disabled:opacity-50" :disabled="!croppedDataUrl" @click="downloadCropped">下载</button>
        </div>

        <NuxtLink to="/image-tools" class="inline-block text-blue-500 mt-2">返回图片工具</NuxtLink>
      </div>

      <!-- 中间：预览与选择区域 -->
      <div class="md:col-span-2">
        <div ref="previewWrap" class="relative border rounded overflow-hidden bg-gray-50" :style="{height: previewHeight + 'px'}">
          <img
            v-if="imgSrc"
            ref="previewImage"
            :src="imgSrc"
            class="block max-w-full max-h-full select-none"
            @load="onImageLoad"
            draggable="false"
          />

          <!-- selection rectangle -->
          <div
            v-if="imgSrc"
            class="absolute border-2 border-dashed border-blue-400 bg-blue-200/10"
            :style="selectionStyle"
            @mousedown.prevent="startDrag"
          ></div>

          <!-- overlay hint when no image -->
          <div v-if="!imgSrc" class="p-8 text-center text-gray-500">请上传图片以开始裁剪（支持拖拽选择区域）</div>
        </div>

        <div class="mt-4">
          <label class="text-sm text-gray-700">裁剪预览</label>
          <div class="mt-2 border rounded p-2 bg-white">
            <img v-if="croppedDataUrl" :src="croppedDataUrl" class="max-w-full" />
            <div v-else class="text-sm text-gray-500">尚未裁剪</div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, computed } from 'vue'
import { useTools } from '@/composables/useTools'
useHead({ title: '图片裁剪 - 图片工具' })

const { readImageFile } = useTools()

const imgSrc = ref(null)
const previewImage = ref(null)
const previewWrap = ref(null)
const previewHeight = 480

const natural = reactive({ width: 0, height: 0 })
const display = reactive({ width: 0, height: 0 })

const aspect = ref(null) // null 或 数值字符串
const outputName = ref('cropped.png')

// selection in displayed pixels
const selection = reactive({ x: 20, y: 20, w: 100, h: 100, active: false })
let dragging = false
let dragStart = { x: 0, y: 0, sx: 0, sy: 0 }

const croppedDataUrl = ref(null)
const rotation = ref(0) // degrees 0/90/180/270

const selectionStyle = computed(() => ({
  left: selection.x + 'px',
  top: selection.y + 'px',
  width: selection.w + 'px',
  height: selection.h + 'px',
  cursor: 'move',
}))

const onFileChange = async (e) => {
  const f = e.target.files && e.target.files[0]
  if (!f) return
  try {
    const res = await readImageFile(f)
    imgSrc.value = res.dataUrl
    // reset
    rotation.value = 0
    croppedDataUrl.value = null
  } catch (err) {
    console.error('读取图片失败', err)
  }
}

const onImageLoad = (e) => {
  const img = e.target
  natural.width = img.naturalWidth
  natural.height = img.naturalHeight
  // set displayed size based on container width
  const wrap = previewWrap.value
  if (wrap) {
    const cw = wrap.clientWidth
    const maxH = previewHeight
    const ratio = natural.width / natural.height
    display.width = cw
    display.height = Math.min(maxH, Math.round(cw / ratio))
  } else {
    display.width = img.clientWidth
    display.height = img.clientHeight
  }

  // initialize selection to center
  selection.w = Math.round(display.width * 0.5)
  selection.h = Math.round(display.height * 0.5)
  selection.x = Math.round((display.width - selection.w) / 2)
  selection.y = Math.round((display.height - selection.h) / 2)
}

const startDrag = (e) => {
  if (!previewImage.value) return
  dragging = true
  selection.active = true
  const rect = previewWrap.value.getBoundingClientRect()
  const mx = e.clientX - rect.left
  const my = e.clientY - rect.top
  dragStart = { x: mx, y: my, sx: selection.x, sy: selection.y }
  window.addEventListener('mousemove', onDrag)
  window.addEventListener('mouseup', endDrag)
}

const onDrag = (e) => {
  if (!dragging) return
  const rect = previewWrap.value.getBoundingClientRect()
  const mx = e.clientX - rect.left
  const my = e.clientY - rect.top
  const dx = mx - dragStart.x
  const dy = my - dragStart.y
  let nx = dragStart.sx + dx
  let ny = dragStart.sy + dy
  // constrain within image
  nx = Math.max(0, Math.min(nx, display.width - selection.w))
  ny = Math.max(0, Math.min(ny, display.height - selection.h))
  selection.x = nx
  selection.y = ny
}

const endDrag = () => {
  dragging = false
  selection.active = false
  window.removeEventListener('mousemove', onDrag)
  window.removeEventListener('mouseup', endDrag)
}

const rotate = (deg) => {
  rotation.value = (rotation.value + deg + 360) % 360
}

const doCrop = async () => {
  if (!imgSrc.value) return
  // compute scale from displayed to natural
  const scaleX = natural.width / display.width
  const scaleY = natural.height / display.height
  let sx = Math.round(selection.x * scaleX)
  let sy = Math.round(selection.y * scaleY)
  let sw = Math.round(selection.w * scaleX)
  let sh = Math.round(selection.h * scaleY)

  // prepare canvas size depending on rotation
  const rot = rotation.value % 360
  let canvasW = sw
  let canvasH = sh
  if (rot === 90 || rot === 270) {
    canvasW = sh
    canvasH = sw
  }

  const canvas = document.createElement('canvas')
  canvas.width = canvasW
  canvas.height = canvasH
  const ctx = canvas.getContext('2d')

  const image = new Image()
  image.src = imgSrc.value
  await new Promise((resolve, reject) => {
    image.onload = resolve
    image.onerror = reject
  })

  // draw rotated
  ctx.save()
  // move origin to center for rotation
  if (rot === 0) {
    ctx.drawImage(image, sx, sy, sw, sh, 0, 0, sw, sh)
  } else if (rot === 90) {
    ctx.translate(canvasW, 0)
    ctx.rotate((90 * Math.PI) / 180)
    ctx.drawImage(image, sx, sy, sw, sh, 0, 0, sw, sh)
  } else if (rot === 180) {
    ctx.translate(canvasW, canvasH)
    ctx.rotate((180 * Math.PI) / 180)
    ctx.drawImage(image, sx, sy, sw, sh, 0, 0, sw, sh)
  } else if (rot === 270) {
    ctx.translate(0, canvasH)
    ctx.rotate((270 * Math.PI) / 180)
    ctx.drawImage(image, sx, sy, sw, sh, 0, 0, sw, sh)
  }
  ctx.restore()

  croppedDataUrl.value = canvas.toDataURL('image/png')
}

const downloadCropped = () => {
  if (!croppedDataUrl.value) return
  const link = document.createElement('a')
  link.href = croppedDataUrl.value
  link.download = outputName.value || 'cropped.png'
  document.body.appendChild(link)
  link.click()
  link.remove()
}
</script>

<!-- 按钮样式使用 Tailwind 类，避免在 SFC 中使用 @apply 导致构建问题 -->
